<?php

declare(strict_types=1);

namespace app\service\product;

use app\model\Org;
use app\model\Prod;
use app\model\ProdBrand;
use app\model\ProdCategory;
use app\util\GenNoUtil;
use InvalidArgumentException;
use RuntimeException;
use think\Collection;
use think\facade\Db;
use think\Paginator;

class ProdService
{
    private const STATUS_TEXT_MAP = [
        '草稿' => 0,
        '上架' => 1,
        '在售' => 1,
        '下架' => 2,
        '停售' => 2,
        '禁用' => 3,
        '禁售' => 3,
    ];

    private const AUDIT_STATUS_TEXT_MAP = [
        '待审核' => 0,
        '审核中' => 0,
        '已通过' => 1,
        '审核通过' => 1,
        '已驳回' => 2,
        '审核拒绝' => 2,
        '审核未通过' => 2,
    ];
    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = Prod::with(['category', 'brand'])
            ->order(['id' => 'desc']);

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('title', '%' . $keyword . '%')
                    ->whereOr('model', 'like', '%' . $keyword . '%')
                    ->whereOr('sku_code', 'like', '%' . $keyword . '%');
            });
        }

        if (!empty($filters['status'])) {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['audit_status'])) {
            $query->where('audit_status', (int) $filters['audit_status']);
        }

        if (!empty($filters['category_id'])) {
            $query->where('category_id', (int) $filters['category_id']);
        }

        if (!empty($filters['brand_id'])) {
            $query->where('brand_id', (int) $filters['brand_id']);
        }

        if (!empty($filters['seller_org_id'])) {
            $query->where('seller_org_id', (int) $filters['seller_org_id']);
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function findById(int $id): ?Prod
    {
        return Prod::with(['category', 'brand'])->find($id);
    }

    public function create(array $payload): Prod
    {
        $payload = $this->normalizeProductInput($payload);
        $data = $this->extractPayload($payload, true);
        $data = $this->ensureRelations($data);
        $data = $this->ensureSkuCode($data);
        $this->assertSkuUnique($data['sku_code'], null);

        /** @var Prod $prod */
        $prod = Db::transaction(function () use ($data) {
            return Prod::create($data);
        });

        return $this->findById((int) $prod->id);
    }

    /**
     * 批量创建商品。
     *
     * @return array{items: Prod[], errors: string[]}
     */
    public function bulkCreate(array $payload): array
    {
        $products = $payload['products'] ?? null;
        if (!is_array($products) || $products === []) {
            throw new InvalidArgumentException('批量商品数据不能为空');
        }

        $base = $payload;
        unset($base['products']);

        $result = [
            'items' => [],
            'errors' => [],
        ];
        foreach ($products as $index => $item) {
            if (!is_array($item)) {
                $result['errors'][] = sprintf('第 %d 条商品数据格式不正确', $index + 1);
                continue;
            }

            try {
                $singlePayload = array_merge($base, $item);
                $created = $this->create($singlePayload);
                $result['items'][] = $created;
            } catch (\Throwable $e) {
                $result['errors'][] = sprintf('第 %d 行：%s', $index + 1, $e->getMessage());
            }
        }

        return $result;
    }

    public function update(Prod $prod, array $payload): Prod
    {
        $payload = $this->normalizeProductInput($payload);
        $data = $this->extractPayload($payload, false);
        $data = $this->ensureRelations($data, (int) $prod->seller_org_id);

        if (array_key_exists('sku_code', $data)) {
            $data = $this->ensureSkuCode($data, true);
            $this->assertSkuUnique((string) $data['sku_code'], (int) $prod->id);
        }

        Db::transaction(function () use ($prod, $data) {
            $prod->save($data);
        });

        return $this->findById((int) $prod->id);
    }

    public function delete(Prod $prod): bool
    {
        return (bool) $prod->delete();
    }

    public function format(Prod $prod): array
    {
        return [
            'id' => (int) $prod->id,
            'seller_org_id' => (int) $prod->seller_org_id,
            'seller_user_id' => $prod->seller_user_id ? (int) $prod->seller_user_id : null,
            'region_id' => $prod->region_id ? (int) $prod->region_id : null,
            'category_id' => (int) $prod->category_id,
            'brand_id' => $prod->brand_id ? (int) $prod->brand_id : null,
            'title' => $prod->title,
            'subtitle' => $prod->subtitle,
            'model' => $prod->model,
            'sku_code' => $prod->sku_code,
            'cover_image' => $prod->cover_image,
            'gallery' => $prod->gallery ?? [],
            'videos' => $prod->videos ?? [],
            'default_price' => (float) $prod->default_price,
            'default_stock' => (int) $prod->default_stock,
            'unit' => $prod->unit,
            'min_order_qty' => (int) $prod->min_order_qty,
            'step_qty' => (int) $prod->step_qty,
            'weight_value' => (float) $prod->weight_value,
            'weight_unit' => $prod->weight_unit,
            'size_length' => (float) $prod->size_length,
            'size_width' => (float) $prod->size_width,
            'size_height' => (float) $prod->size_height,
            'origin_country' => $prod->origin_country,
            'manufactured_at' => $prod->manufactured_at,
            'summary' => $prod->summary,
            'description' => $prod->description,
            'attachments' => $prod->attachments ?? [],
            'attributes' => $prod->attributes ?? [],
            'status' => (int) $prod->status,
            'audit_status' => (int) $prod->audit_status,
            'publish_time' => $prod->publish_time,
            'views' => (int) $prod->views,
            'favorites' => (int) $prod->favorites,
            'remark' => $prod->remark,
            'create_time' => $prod->create_time,
            'update_time' => $prod->update_time,
            'category' => $prod->category ? [
                'id' => (int) $prod->category->id,
                'name' => $prod->category->name,
            ] : null,
            'brand' => $prod->brand ? [
                'id' => (int) $prod->brand->id,
                'name' => $prod->brand->name,
            ] : null,
        ];
    }

    public function formatCollection(iterable $products): array
    {
        $results = [];
        foreach ($products as $prod) {
            if ($prod instanceof Prod) {
                $results[] = $this->format($prod);
            }
        }

        return $results;
    }

    private function extractPayload(array $payload, bool $isCreate): array
    {
        $fields = [
            'seller_org_id',
            'seller_user_id',
            'region_id',
            'category_id',
            'brand_id',
            'title',
            'subtitle',
            'model',
            'sku_code',
            'cover_image',
            'gallery',
            'videos',
            'default_price',
            'default_stock',
            'unit',
            'min_order_qty',
            'step_qty',
            'weight_value',
            'weight_unit',
            'size_length',
            'size_width',
            'size_height',
            'origin_country',
            'manufactured_at',
            'summary',
            'description',
            'attachments',
            'attributes',
            'status',
            'audit_status',
            'publish_time',
            'remark',
        ];

        $data = array_intersect_key($payload, array_flip($fields));

        foreach (['seller_org_id', 'seller_user_id', 'region_id', 'category_id', 'brand_id', 'default_stock', 'min_order_qty', 'step_qty', 'status', 'audit_status'] as $intField) {
            if (array_key_exists($intField, $data) && $data[$intField] !== null && $data[$intField] !== '') {
                $data[$intField] = (int) $data[$intField];
            }
        }

        foreach (['default_price', 'weight_value', 'size_length', 'size_width', 'size_height'] as $floatField) {
            if (array_key_exists($floatField, $data) && $data[$floatField] !== null && $data[$floatField] !== '') {
                $data[$floatField] = (float) $data[$floatField];
            }
        }

        foreach (['gallery', 'videos', 'attachments', 'attributes'] as $jsonField) {
            if (array_key_exists($jsonField, $data)) {
                $value = $data[$jsonField];
                if ($value === null || $value === '') {
                    $data[$jsonField] = [];
                } elseif (!is_array($value)) {
                    throw new InvalidArgumentException($jsonField . ' 必须是数组');
                }
            }
        }

        if ($isCreate) {
            foreach (['gallery', 'videos', 'attachments', 'attributes'] as $field) {
                $data[$field] = $data[$field] ?? [];
            }
        }

        return $data;
    }

    private function ensureSkuCode(array $data, bool $isUpdate = false): array
    {
        if (!array_key_exists('sku_code', $data)) {
            if ($isUpdate) {
                return $data;
            }
            $data['sku_code'] = $this->generateUniqueSkuCode();
            return $data;
        }

        $sku = strtoupper(trim((string) ($data['sku_code'] ?? '')));
        if ($sku === '') {
            $sku = $this->generateUniqueSkuCode();
        }

        $data['sku_code'] = $sku;
        return $data;
    }

    private function generateUniqueSkuCode(int $attempts = 5): string
    {
        while ($attempts > 0) {
            $attempts--;
            $sku = GenNoUtil::sku();
            if (Prod::where('sku_code', $sku)->count() === 0) {
                return $sku;
            }
        }

        throw new RuntimeException('生成唯一 SKU 编码失败，请稍后重试');
    }

    public function normalizeProductInput(array $payload): array
    {
        $payload = $this->populateAliasFields($payload);
        $payload = $this->assignCategoryIdByText($payload);
        $payload = $this->assignBrandIdByText($payload);
        $payload = $this->ensureTitleFromFallback($payload);
        $payload = $this->normalizeStatusFields($payload);

        return $payload;
    }

    private function assignCategoryIdByText(array $payload): array
    {
        $categoryId = isset($payload['category_id']) ? (int) $payload['category_id'] : 0;
        if ($categoryId > 0) {
            return $payload;
        }

        $slugCandidates = $this->collectStringValues($payload, [
            'category_slug',
            'category_code',
            'category_identifier',
        ]);

        foreach ($slugCandidates as $slug) {
            $category = ProdCategory::where('slug', $slug)->find();
            if ($category) {
                $payload['category_id'] = (int) $category->id;
                return $payload;
            }
        }

        $nameCandidates = $this->collectCategoryNameCandidates($payload);
        if ($nameCandidates === []) {
            return $payload;
        }

        foreach ($nameCandidates as $name) {
            $category = ProdCategory::where('name', $name)->find();
            if ($category) {
                $payload['category_id'] = (int) $category->id;
                return $payload;
            }
        }

        throw new InvalidArgumentException(sprintf('分类“%s”不存在', $nameCandidates[0]));
    }

    private function assignBrandIdByText(array $payload): array
    {
        $brandId = isset($payload['brand_id']) ? (int) $payload['brand_id'] : 0;
        if ($brandId > 0) {
            return $payload;
        }

        $slugCandidates = $this->collectStringValues($payload, [
            'brand_slug',
            'brand_code',
            'brand_identifier',
        ]);

        foreach ($slugCandidates as $slug) {
            $brand = ProdBrand::where('slug', $slug)->find();
            if ($brand) {
                $payload['brand_id'] = (int) $brand->id;
                return $payload;
            }
        }

        $nameCandidates = $this->collectStringValues($payload, [
            'brand_name',
            'brand',
            'brand_label',
        ]);

        if ($nameCandidates === []) {
            return $payload;
        }

        foreach ($nameCandidates as $name) {
            $brand = ProdBrand::where('name', $name)->find();
            if ($brand) {
                $payload['brand_id'] = (int) $brand->id;
                return $payload;
            }
        }

        $ownerOrgId = isset($payload['seller_org_id']) ? (int) $payload['seller_org_id'] : null;
        $brand = $this->createBrandByName($nameCandidates[0], $ownerOrgId);
        $payload['brand_id'] = (int) $brand->id;

        return $payload;
    }

    private function ensureTitleFromFallback(array $payload): array
    {
        $title = trim((string) ($payload['title'] ?? ''));
        if ($title !== '') {
            return $payload;
        }

        $parts = $this->collectStringValues($payload, [
            'brand_name',
            'brand',
            'brand_slug',
        ]);

        $model = trim((string) ($payload['model'] ?? ''));
        if ($model !== '' && !in_array($model, $parts, true)) {
            $parts[] = $model;
        }

        if ($parts !== []) {
            $payload['title'] = implode(' ', $parts);
        }

        return $payload;
    }

    private function normalizeStatusFields(array $payload): array
    {
        if (array_key_exists('status', $payload)) {
            $payload['status'] = $this->convertTextEnum($payload['status'], self::STATUS_TEXT_MAP);
        }

        if (array_key_exists('audit_status', $payload)) {
            $payload['audit_status'] = $this->convertTextEnum($payload['audit_status'], self::AUDIT_STATUS_TEXT_MAP);
        }

        return $payload;
    }

    private function collectCategoryNameCandidates(array $payload): array
    {
        $names = $this->collectStringValues($payload, [
            'category_level2',
            'category_secondary',
            'category_secondary_name',
            'category_name',
            'category',
            'category_label',
            'category_level1',
            'category_primary',
        ]);

        $path = trim((string) ($payload['category_path'] ?? ''));
        if ($path !== '') {
            $segments = preg_split('/[>\/\\\\,\-，]+/u', $path) ?: [];
            $segments = array_filter(array_map(static fn($value) => trim((string) $value), $segments), static fn($value) => $value !== '');
            foreach ($segments as $segment) {
                array_unshift($names, $segment);
            }
        }

        return array_values(array_unique($names));
    }

    private function collectStringValues(array $payload, array $keys): array
    {
        $values = [];
        foreach ($keys as $key) {
            if (!array_key_exists($key, $payload)) {
                continue;
            }

            $raw = $payload[$key];
            if (is_array($raw)) {
                foreach ($raw as $item) {
                    $item = trim((string) $item);
                    if ($item !== '') {
                        $values[] = $item;
                    }
                }
                continue;
            }

            $value = trim((string) $raw);
            if ($value !== '') {
                $values[] = $value;
            }
        }

        return array_values(array_unique($values));
    }

    private function convertTextEnum(mixed $value, array $map): mixed
    {
        $value = $this->extractScalarValue($value);
        if ($value === null || $value === '') {
            return $value;
        }

        if (is_numeric($value)) {
            return (int) $value;
        }

        $text = mb_strtolower(trim((string) $value));
        if ($text === '') {
            return $value;
        }

        foreach ($map as $label => $code) {
            if ($text === mb_strtolower($label)) {
                return $code;
            }
        }

        $display = is_scalar($value) ? (string) $value : json_encode($value, JSON_UNESCAPED_UNICODE);
        throw new InvalidArgumentException(sprintf('无法识别的状态值：%s', $display));
    }

    private function populateAliasFields(array $payload): array
    {
        $aliases = (array) config('product.field_aliases', []);
        if ($aliases === []) {
            return $payload;
        }

        $normalizedMap = [];
        foreach ($payload as $key => $value) {
            $normalizedMap[$this->normalizeFieldName((string) $key)] = $key;
        }

        foreach ($aliases as $canonical => $aliasList) {
            if (array_key_exists($canonical, $payload) && $this->hasNonEmptyValue($payload[$canonical] ?? null)) {
                continue;
            }

            foreach ((array) $aliasList as $alias) {
                $normalizedAlias = $this->normalizeFieldName((string) $alias);
                if (isset($normalizedMap[$normalizedAlias])) {
                    $sourceKey = $normalizedMap[$normalizedAlias];
                    if ($this->hasNonEmptyValue($payload[$sourceKey] ?? null)) {
                        $payload[$canonical] = $payload[$sourceKey];
                        break;
                    }
                }
            }
        }

        return $payload;
    }

    private function normalizeFieldName(string $key): string
    {
        $normalized = preg_replace('/[^a-z0-9]+/i', '', strtolower($key));
        return $normalized ?? strtolower($key);
    }

    private function hasNonEmptyValue(mixed $value): bool
    {
        if ($value === null) {
            return false;
        }

        if (is_string($value)) {
            return trim($value) !== '';
        }

        if (is_array($value)) {
            return $value !== [];
        }

        return true;
    }

    private function extractScalarValue(mixed $value): mixed
    {
        if (!is_array($value)) {
            return $value;
        }

        foreach ($value as $item) {
            $candidate = $this->extractScalarValue($item);
            if ($candidate === null) {
                continue;
            }

            if (is_string($candidate) && trim($candidate) === '') {
                continue;
            }

            return $candidate;
        }

        return null;
    }

    private function createBrandByName(string $name, ?int $sellerOrgId = null): ProdBrand
    {
        $slug = $this->generateUniqueBrandSlug($name);

        /** @var ProdBrand $brand */
        $brand = ProdBrand::create([
            'name' => $name,
            'slug' => $slug,
            'logo_url' => '',
            'banner_url' => '',
            'description' => '',
            'content' => null,
            'origin_country' => '',
            'owner_org_id' => $sellerOrgId,
            'sort' => 0,
            'is_visible' => 1,
        ]);

        return $brand;
    }

    private function generateUniqueBrandSlug(string $name): string
    {
        $normalized = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $name) ?? ''));
        $base = trim($normalized, '-') ?: 'brand';
        $slug = $base;
        $suffix = 1;
        while (ProdBrand::where('slug', $slug)->count() > 0) {
            $slug = $base . '-' . $suffix;
            $suffix++;
        }

        return $slug;
    }

    /**
     * 验证商品关联的类目/品牌/卖家组织是否存在。
     */
    private function ensureRelations(array $data, ?int $originalSellerOrgId = null): array
    {
        if (isset($data['category_id'])) {
            $categoryExists = ProdCategory::where('id', $data['category_id'])->count() > 0;
            if (!$categoryExists) {
                throw new InvalidArgumentException('商品分类不存在');
            }
        }

        if (isset($data['brand_id']) && $data['brand_id']) {
            $brandExists = ProdBrand::where('id', $data['brand_id'])->count() > 0;
            if (!$brandExists) {
                throw new InvalidArgumentException('品牌不存在');
            }
        }

        if (isset($data['seller_org_id'])) {
            /** @var Org|null $sellerOrg */
            $sellerOrg = Org::where('id', $data['seller_org_id'])->find();
            if (!$sellerOrg) {
                throw new InvalidArgumentException('卖家组织不存在');
            }
            $data['region_id'] = $sellerOrg->region_id ? (int) $sellerOrg->region_id : null;
        } elseif ($originalSellerOrgId !== null) {
            $data['seller_org_id'] = $originalSellerOrgId;
            /** @var Org|null $sellerOrg */
            $sellerOrg = Org::where('id', $originalSellerOrgId)->find();
            if ($sellerOrg) {
                $data['region_id'] = $sellerOrg->region_id ? (int) $sellerOrg->region_id : null;
            }
        }

        return $data;
    }

    private function assertSkuUnique(string $sku, ?int $excludeId): void
    {
        if ($sku === '') {
            return;
        }

        $query = Prod::where('sku_code', $sku);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('SKU 编码已存在');
        }
    }
}
